列表和列表项是FreeRTOS 的一个数据结构,FreeRTOS 大量使用到了列表和列表项,它是FreeRTOS 的基石。

  • 什么是列表和列表项
  • 列表和列表项的初始化
  • 列表项的插入
  • 列表项末尾插入
  • 列表项的删除
  • 列表项的遍历
  • 列表项的插入和删除实验

##什么是列表和列表项?

列表

列表是 FreeRTOS 中的一个数据结构,概念上和链表有点类似,列表被用来跟踪 FreeRTOS中的任务。与列表相关的全部东西都在文件 list.c 和 list.h 中。

1
2
3
4
5
6
7
8
typedef struct xLIST
{
listFIRST_LIST_INTEGRITY_CHECK_VALUE (1)
configLIST_VOLATILE UBaseType_t uxNumberOfItems; (2)
ListItem_t *configLIST_VOLATILE pxIndex; (3)
MiniListItem_t xListEnd; (4)
listSECOND_LIST_INTEGRITY_CHECK_VALUE (5)
} List_t;

(1)和(5)、这两个都是用来检查列表完整性的,需要将宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 设置为 1,开启以后会向这两个地方分别添加一个变量 xListIntegrityValue1 和xListIntegrityValue2,在初始化列表的时候会这两个变量中写入一个特殊的值,默认不开启这个功能。

(2)、 uxNumberOfItems 用来记录列表中列表项的数量。

(3)、 pxIndex 用来记录当前列表项索引号,用于遍历列表。

(4)、 列表中最后一个列表项,用来表示列表结束,此变量类型为 MiniListItem_t,这是一个
迷你列表项,关于列表项稍后讲解。

列表项

FreeRTOS提供了两种列表项:列表项和迷你列表项,两个都在文件 list.h 中有定义。

1
2
3
4
5
6
7
8
9
10
11
struct xLIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE (1)
configLIST_VOLATILE TickType_t xItemValue; (2)
struct xLIST_ITEM * configLIST_VOLATILE pxNext; (3)
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; (4)
void * pvOwner; (5)
void * configLIST_VOLATILE pvContainer; (6)
listSECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE (7)
};
typedef struct xLIST_ITEM ListItem_t;

(1)和(7)、用法和列表一样,用来检查列表项完整性的。
(2)、 xItemValue 为列表项值。
(3)、 pxNext 指向下一个列表项。
(4)、 pxPrevious 指向前一个列表项,和 pxNext 配合起来实现类似双向链表的功能。
(5)、 pvOwner 记录此链表项归谁拥有,通常是任务控制块。
(6)、 pvContainer 用来记录此列表项归哪个列表。注意和 pvOwner 的区别,在前面讲解任务控制块 TCB_t 的时候说了在 TCB_t 中有两个变量 xStateListItem 和 xEventListItem,这两个变量的类型就是 ListItem_t,也就是说这两个成员变量都是列表项。以 xStateListItem 为例,当创建一个任务以后 xStateListItem 的 pvOwner 变量就指向这个任务的任务控制块,表示 xSateListItem属于此任务。当任务就绪态以后 xStateListItem 的变量pvContainer 就指向就绪列表,表明此列表项在就绪列表中。

列表项
xItemValue
pxNext
pxPrevious
pvOwner
pvContainer

迷你列表项

迷你列表项在文件 list.h 中有定义。

1
2
3
4
5
6
7
8
struct xMINI_LIST_ITEM
{
listFIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE (1)
configLIST_VOLATILE TickType_t xItemValue; (2)
struct xLIST_ITEM * configLIST_VOLATILE pxNext; (3)
struct xLIST_ITEM * configLIST_VOLATILE pxPrevious; (4)
};
typedef struct xMINI_LIST_ITEM MiniListItem_t;

(1)、用于检查迷你列表项的完整性。
(2)、 xItemValue 记录列表列表项值。
(3)、 pxNext 指向下一个列表项。
(4)、 pxPrevious 指向上一个列表项。
可以看出迷你列表项只是比列表项少了几个成员变量,迷你列表项有的成员变量列表项都有的。

Mini 列表项
xItemValue
pxNext
pxPrevious

列表和列表项初始化

列表初始化

新创建或者定义的列表需要对其做初始化处理,列表的初始化其实就是初始化列表结构体List_t 中的各个成员变量,列表的初始化通过使函数 vListInitialise( )来完成,此函数在 list.c 中有定义。

1
2
3
4
5
6
7
8
9
10
void vListInitialise( List_t * const pxList )
{
pxList->pxIndex = ( ListItem_t * ) &( pxList->xListEnd ); (1)
pxList->xListEnd.xItemValue = portMAX_DELAY; (2)
pxList->xListEnd.pxNext = ( ListItem_t * ) &( pxList->xListEnd ); (3)
pxList->xListEnd.pxPrevious = ( ListItem_t * ) &( pxList->xListEnd ); (4)
pxList->uxNumberOfItems = ( UBaseType_t ) 0U; (5)
listSET_LIST_INTEGRITY_CHECK_1_VALUE( pxList ); (6)
listSET_LIST_INTEGRITY_CHECK_2_VALUE( pxList ); (7)
}

(1)、 xListEnd 用来表示列表的末尾,而 pxIndex 表示列表项的索引号,此时列表只有一个列表项,那就是 xListEnd,所以 pxIndex 指向 xListEnd。

(2)、 xListEnd 的列表项值初始化为 portMAX_DELAY, portMAX_DELAY 是个宏,在文件portmacro.h 中有定义。根据所使用的 MCU 的不同, portMAX_DELAY 值也不相同,可以为 0xffff或者 0xffffffffUL,本教程中为 0xffffffffUL。

(3)、初始化列表项 xListEnd 的 pxNext 变量,因为此时列表只有一个列表项 xListEnd,因此 pxNext 只能指向自身。

(4)、同(3)一样,初始化 xListEnd 的 pxPrevious 变量,指向 xListEnd 自身。

(5)、由于此时没有其他的列表项,因此 uxNumberOfItems 为 0,注意,这里没有算 xListEnd。

(6) 和 (7) 、 初始化列表项中用于完整性检查字段,只有宏configUSE_LIST_DATA_INTEGRITY_CHECK_BYTES 为 1 的时候才有效。同样的根据所选的MCU 不同其写入的值也不同,可以为 0x5a5a 或者 0x5a5a5a5aUL。STM32是32 位系统写入的是 0x5a5a5a5aUL。

列表项初始化

函数vListInitialiseItem()完成列表项初始化

1
2
3
4
5
6
7
8
void vListInitialiseItem( ListItem_t * const pxItem )
{
pxItem->pvContainer = NULL; //初始化 pvContainer 为 NULL

//初始化用于完整性检查的变量,如果开启了这个功能的话。
listSET_FIRST_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
listSET_SECOND_LIST_ITEM_INTEGRITY_CHECK_VALUE( pxItem );
}

列表项的初始化很简单,只是将列表项成员变量 pvContainer 初始化为 NULL,并且给用于
完整性检查的变量赋值。

列表项插入

列表项插入函数分析

列表项的插入操作通过函数 vListInsert()来完成,函数原型如下:

1
2
void vListInsert( List_t * const      pxList,
ListItem_t * const pxNewListItem )

参数
pxList: 列表项要插入的列表。
pxNewListItem: 要插入的列表项。

返回值:

函数 vListInsert()的参数 pxList 决定了列表项要插入到哪个列表中, pxNewListItem 决定了要插入的列表项,但是这个列表项具体插入到什么地方呢?要插入的位置由列表项中成员变量xItemValue 来决定。 列表项的插入根据 xItemValue 的值按照升序的方式排列!

列表项末尾插入

列表项末尾插入函数分析

列表末尾插入列表项的操作通过函数 vListInsertEnd ()来完成,函数原型如下:

1
2
void vListInsertEnd( List_t * const      pxList,
ListItem_t * const pxNewListItem )

参数:
pxList: 列表项要插入的列表。
pxNewListItem: 要插入的列表项。

列表项的删除

有列表项的插入,那么必然有列表项的删除,列表项的删除通过函数 uxListRemove()来完
成,函数原型如下:

1
UBaseType_t uxListRemove( ListItem_t * const pxItemToRemove )

参数:
pxItemToRemove: 要删除的列表项。

返回值:
返回删除列表项以后的列表剩余列表项数目。
注意,列表项的删除只是将指定的列表项从列表中删除掉,并不会将这个列表项的内存给释放掉!如果这个列表项是动态分配内存的话。

列表的遍历

介绍列表结构体的时候说过列表 List_t 中的成员变量 pxIndex 是用来遍历列表的,FreeRTOS提供了一个函数来完成列表的遍历,这个函数是 listGET_OWNER_OF_NEXT_ENTRY()。每调用一次这个函数列表的 pxIndex 变量就会指向下一个列表项,并且返回这个列表项的 pxOwner变量值。这个函数本质上是一个宏,这个宏在文件 list.h 中如下定义